/*
 * Copyright 2009, Gregg Tavares
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Gregg Tavares nor the names of his
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

o3djs.require('o3djs.util');
o3djs.require('o3djs.camera');
o3djs.require('o3djs.rendergraph');
o3djs.require('o3djs.shape');
o3djs.require('o3djs.dump');
o3djs.require('o3djs.effect');
o3djs.require('o3djs.material');
o3djs.require('o3djs.primitives');
o3djs.require('o3djs.pack');
o3djs.require('o3djs.arcball');
o3djs.require('o3djs.scene');
o3djs.require('o3djs.canvas');

/**
 * Creates a __WindowShadow object.
 * @constructor
 * @param {!o3d.Pack} pack Pack used to hold shadow assets.
 */
function __WindowShadow(pack) {
  this.subDivisions = 8;
  this.vertsPerCorner = this.subDivisions + 1;

  var indices = [0, 1, 2,
                 0, 2, 3];
  for (var cc = 0; cc < 4; ++cc) {
    var offset = 4 + cc * this.vertsPerCorner;
    for (var ii = 0; ii < this.subDivisions; ++ii) {
      indices.push(offset + ii);
      indices.push(offset + ii + 1);
      indices.push(cc);
    }
    var nextCorner = (cc + 1) % 4;
    indices.push(offset + this.vertsPerCorner - 1);
    indices.push(4 + nextCorner * this.vertsPerCorner);
    indices.push(nextCorner);
    indices.push(offset + this.vertsPerCorner - 1);
    indices.push(nextCorner);
    indices.push(cc);
  }
  var colors = [1, 1, 1, 1,
                1, 1, 1, 1,
                1, 1, 1, 1,
                1, 1, 1, 1];
  for (var ii = 0; ii < this.vertsPerCorner * 4; ++ii) {
    colors.push(1, 1, 1, 0);
  }

  // create a shape and primitive for the vertices.
  this.shape = pack.createObject('Shape');
  this.primitive = pack.createObject('Primitive');
  this.streamBank = pack.createObject('StreamBank');
  this.primitive.owner = this.shape;

  this.primitive.numberPrimitives = indices.length / 3;
  this.primitive.primitiveType = o3djs.base.o3d.Primitive.TRIANGLELIST;
  this.primitive.numberVertices = 4 + 4 * this.vertsPerCorner;
  this.primitive.streamBank = this.streamBank;

  this.vertexBuffer = pack.createObject('VertexBuffer');
  this.positionField = this.vertexBuffer.createField('FloatField', 3);
  this.colorField = this.vertexBuffer.createField('FloatField', 4);
  this.vertexBuffer.allocateElements(this.primitive.numberVertices);
  this.streamBank.setVertexStream(o3djs.base.o3d.Stream.POSITION,
                                  0,
                                  this.positionField,
                                  0);
  this.streamBank.setVertexStream(o3djs.base.o3d.Stream.COLOR,
                                  0,
                                  this.colorField,
                                  0);
  this.colorField.setAt(0, colors);
  this.setSize(2, 2, 1);

  this.indexBuffer = pack.createObject('IndexBuffer');
  this.indexBuffer.set(indices);
  this.primitive.indexBuffer = this.indexBuffer;
}

/**
 * Creates a __WindowShadow object.
 * @param {o3d.Pack} pack Pack used to hold shadow assets.
 * @return {Object} the created __WindowShadow.
 */
function WindowShadow(pack) {
  return new __WindowShadow(pack);
}

/**
 * Sets the size of the shadow by updating the vertices.
 * @param {number} width Width of shadow.
 * @param {number} height Height of shadow.
 * @param {number} radius Radius of fade out area around shadow.
 */
__WindowShadow.prototype.setSize = function(width, height, radius) {
  var verts = [0, 0, 0,
               width, 0, 0,
               width, 0, -height,
               0, 0, -height];
  var xSignTable = [-1, +1, +1, -1];
  var zSignTable = [+1, +1, -1, -1];
  for (var ii = 0; ii <= this.subDivisions; ++ii) {
    var theta = Math.PI * 0.5 * ii / this.subDivisions + Math.PI * 1.5;
    for (var cc = 0; cc < 4; ++cc) {
      var sin = Math.sin(theta + Math.PI * 0.5 * cc) * radius;
      var cos = Math.cos(theta + Math.PI * 0.5 * cc) * radius;
      var offset = (4 + cc * this.vertsPerCorner + ii) * 3;
      var cOffset = cc * 3;
      verts[offset + 0] = verts[cOffset + 0] + sin;
      verts[offset + 1] = verts[cOffset + 1] + 0;
      verts[offset + 2] = verts[cOffset + 2] + cos;
    }
  }
  this.positionField.setAt(0, verts);
};

// ********************************* UI **********************************

/**
 * Cleans up on exit.
 */
function uninit() {
  g_client.cleanup();
}

/**
 * a __UI manages a client area as a UI surface.
 * @constructor
 * @param {Element} o3dElement the client3d DOM element.
 */
function __UI(o3dElement) {
  this.o3dElement = o3dElement;
  this.client = g_client;
  this.pack = this.client.createPack();
  this.mouseMoveCallback = null;
  this.mouseUpCallback = null;
  this.mainWindow = new __BackWindow();
  this.mainWindow.init({ ui: this,
                         title: 'desktop',
                         rect: createRect(0,
                                          0,
                                          this.client.width,
                                          this.client.height),
                         priority: 0,
                         renderGraphRoot: this.client.renderGraphRoot });
  this.onRenderWindows = [];

  var that = this;  // so the functions below can see 'that'

  /**
   * Handles mouse down events from the browser.
   * @param {Object} e Event info.
   */
  var onMouseDown = function(e) {
    if (e.shiftKey) {
      onContextMenu(e);
    } else {
      that.onMouseDown(e);
    }
  };

  /**
   * Handles mouse move events from the browser.
   * @param {Object} e Event info.
   */
  var onMouseMove = function(e) {
    that.onMouseMove(e);
  }

  /**
   * Handles mouse up events from the browser.
   * @param {Object} e Event info.
   */
  var onMouseUp = function(e) {
    that.onMouseUp(e);
  };

  /**
   * Handles mouse scroll events from the browser.
   * @param {Object} e Event info.
   */
  var onMouseScroll = function(e) {
    var amount = e.deltaY;
    var offset = {x: e.x, y: e.y};
    that.onMouseScroll({relativeMousePosition: offset,
                        scrollAmount: amount});
  };

  var appIndex = 0;

  /**
   * Handles context menu events from the browser.
   * @param {Object} e Event info.
   */
  var onContextMenu = function(e) {
    var left = e.x - 200;
    var top = e.y - 5;
    if (left < 0) {
      left = 0;
    }
    if (top < 0) {
      top = 0;
    }

    var appWindow;
    switch (appIndex) {
    case 1:
      appWindow = new __SimpleViewerWindow(
          getURLFromPath('assets/teapot.o3dtgz'));
      break;
    case 2:
      appWindow = new __InstanceOverrideWindow();
      break;
    case 3:
      appWindow = new __SimpleViewerWindow(
          getURLFromPath('assets/seven_shapes.o3dtgz'));
      break;
    case 4:
      appWindow = new __SimpleViewerWindow(
          getURLFromPath('assets/yard.o3dtgz'));
      break;
    case 5:
      appWindow = new __SimpleViewerWindow(
          getURLFromPath('assets/cube.o3dtgz'));
      break;
    default:
      appWindow = new __SpinningCubeWindow();
      appIndex = 0;
      break;
    }

    ++appIndex;

    that.createCanvasWindow({ title: 'app',
                              rect: createRect(left, top, 400, 300),
                              backgroundColor: [1, 0, 0, 1],
                              appWindow: appWindow});
  };

  /**
   * Handles mouse key down events from the browser.
   * @param {Object} e Event info.
   */
  var onKeyDown = function(e) {
  };

  /**
   * Handles on Render events from the client.
   * @param {o3d.RenderEvent} renderEvent RenderEvent info.
   */
  var onRender = function(renderEvent) {
    that.onRender(renderEvent.elapsedTime);
  };

  o3djs.event.addEventListener(o3dElement, 'mousedown', onMouseDown);
  o3djs.event.addEventListener(o3dElement, 'mousemove', onMouseMove);
  o3djs.event.addEventListener(o3dElement, 'mouseup', onMouseUp);
  o3djs.event.addEventListener(o3dElement, 'keydown', onKeyDown);
  o3djs.event.addEventListener(o3dElement, 'wheel', onMouseScroll);
  o3djs.event.addEventListener(o3dElement, 'contextmenu', onContextMenu);

  this.client.setRenderCallback(onRender);
}

/**
 * Creates a __UI object.
 * @param {o3dElement} o3dElement the client3d DOM element.
 * @return {Object} The created __UI object.
 */
function createUI(o3dElement) {
  return new __UI(o3dElement);
}

/**
 * Creates a CanvasWindow in this UI.
 * @param {Object} spec The initialization parameters.
 * @return {Object} The created Window.
 */
__UI.prototype.createCanvasWindow = function(spec) {
  spec.parent = this.mainWindow;
  spec.priority = this.mainWindow.subWindows.length;
  spec.pack = this.pack;
  spec.minWidth = 80;
  spec.minHeight = 60;
  var window = new __CanvasWindow();
  window.init(spec);
  this.mainWindow.subWindows.push(window);
  return window;
};

/**
 * Gets the width of the client area being managed by this UI object.
 * @return {number} The width of the client area.
 */
__UI.prototype.getWidth = function() {
  return this.mainWindow.rect.width;
};

/**
 * Gets the height of the client area being managed by this UI object.
 * @return {number} The height of the client area.
 */
__UI.prototype.getHeight = function() {
  return this.mainWindow.rect.height;
};

/**
 * Adds a window to have its onRender function called once per frame.
 * @param {Object} renderWindow Window to add.
 */
__UI.prototype.addOnRenderWindow = function(renderWindow) {
  this.onRenderWindows.push(renderWindow);
};

/**
 * Removes of window from the list of windows to call onRender functions on.
 * @param {Object} renderWindow Window to remove.
 */
__UI.prototype.removeOnRenderWindow = function(renderWindow) {
  var newWindows = [];
  for (var ii = 0; ii < this.onRenderWindows.length; ++ii) {
    if (renderWindow !== this.onRenderWindows[ii]) {
      newWindows.push(this.onRenderWindows[ii]);
    }
  }
  this.onRenderWindows = newWindows;
};

/**
 * Handler for onRender.
 * @param {number} elapsedTime time in seconds since the last call to onRender.
 */
__UI.prototype.onRender = function(elapsedTime) {
  this.updateViewport();
  for (var ii = 0; ii < this.onRenderWindows.length; ++ii) {
    this.onRenderWindows[ii].onRender(elapsedTime);
  }
};

/**
 * Records the new size of the client area and calls resizeWindow on all
 * child windows.
 * TODO(gman): Change it to call onResize
 */
__UI.prototype.updateViewport = function() {
  // Get the width and height of our client area. We will need this to create
  // a projection matrix.
  var newWidth = parseInt(this.client.width);
  var newHeight = parseInt(this.client.height);

  if (newWidth != this.mainWindow.rect.width ||
      newHeight != this.mainWindow.rect.height) {
    this.mainWindow.resizeWindow(createPoint(newWidth, newHeight));
  }
};

/**
 * Sets the callback to handle mouse move events.
 * @param {function} func The callback to call.
 */
__UI.prototype.setMouseMoveCallback = function(func) {
  this.mouseMoveCallback = func;
};

/**
 * Sets the callback to handle mouse up events.
 * @param {function} func The callback to call.
 */
__UI.prototype.setMouseUpCallback = function(func) {
  this.mouseUpCallback = func;
};


/**
 * Handles mouse down events.
 * @param {Point} mousePosition Position of mouse relative to client area.
 */
__UI.prototype.onMouseDown = function(mousePosition) {
  this.mainWindow.onMouseDown(mousePosition);
};

/**
 * Handles mouse move events.
 * @param {Point} mousePosition Position of mouse relative to client area.
 */
__UI.prototype.onMouseMove = function(mousePosition) {
  if (this.mouseMoveCallback) {
    this.mouseMoveCallback(mousePosition);
  }
};

/**
 * Handles mouse up events.
 * @param {Point} mousePosition Position of mouse relative to client area.
 */
__UI.prototype.onMouseUp = function(mousePosition) {
  if (this.mouseUpCallback) {
    this.mouseUpCallback(mousePosition);
  }
};

/**
 * Handles mouse scroll events.
 * @param {Object} event Information about the mouse.
 */
__UI.prototype.onMouseScroll = function(event) {
  this.mainWindow.onMouseScroll(event);
};

// ********************************* Point **********************************

/**
 * A class that represents a 2d point.
 * @param {number} x x coordinate.
 * @param {number} y y coordinate.
 * @constructor
 */
function __Point(x, y) {
  this.x = x;
  this.y = y;
}

/**
 * Creates a copy of this point.
 * @return {Object} Created point.
 */
__Point.prototype.clone = function() {
  return createPoint(this.x, this.y);
};

/**
 * Creates a Point object.
 * @param {number} x x coordinate.
 * @param {number} y y coordinate.
 * @return {Object} Created point.
 */
function createPoint(x, y) {
  return new __Point(x, y);
}

// ********************************* Rect **********************************

/**
 * A class that represents a 2d rectangle
 * @param {number} left left edge of rectangle.
 * @param {number} top top of rectangle.
 * @param {number} width width of rectangle.
 * @param {number} height height of rectangle.
 * @constructor
 */
function __Rect(left, top, width, height) {
  this.width = width;
  this.height = height;
  this.left = left;
  this.top = top;
}

/**
 * Creates a copy of this Rect.
 * @return {Object} Created Rect.
 */
__Rect.prototype.clone = function() {
  return createRect(this.left, this.top, this.width, this.height);
};

/**
 * Tests if a point is inside the rectangle.
 * @param {Point} point to check.
 * @return {boolean} true if point is inside rectangle.
 */
__Rect.prototype.pointInRect = function(point) {
  return point.x >= this.left && point.x < this.left + this.width &&
         point.y >= this.top && point.y < this.top + this.height;
};

/**
 * Returns a point relative to the rectange.
 * @param {Point} point to make relative.
 * @return {Object} The relative point.
 */
__Rect.prototype.getRelativePosition = function(point) {
  return createPoint(point.x - this.left, point.y - this.top);
};

/**
 * Creates a Rect.
 * @param {number} left left edge of rectangle.
 * @param {number} top top of rectangle.
 * @param {number} width width of rectangle.
 * @param {number} height height of rectangle.
 * @return {Object} Created Rect.
 */
function createRect(left, top, width, height) {
  return new __Rect(left, top, width, height);
}

// ******************************* Window **********************************

/**
 * The base class for all Windows.
 * The basic definition of a window is a rectangular area that manages child
 * windows. It may or may not have a visual representation.
 * @constructor.
 */
function __Window() {
  this.wType = "Window";
}

/**
 * Initialzes a Window
 * @param {Object} spec Initialization parameters.
 */
__Window.prototype.init = function(spec) {
  this.parent = spec.parent;
  this.title = spec.title;
  this.rect = spec.rect.clone();
  this.priority = spec.priority;
  this.dragSlave = spec.dragSlave;
  this.closeSlave = spec.closeSlave;
  this.minWidth = spec.minWidth || 5;
  this.minHeight = spec.minHeight || 5;
  this.subWindows = [];
  // TODO(gman): refactor to pass in pack or something.
  var pack = spec.pack;
  if (!pack) {
    pack = g_pack;  // spec.pack;
  }
  this.pack = pack;
  var renderGraphRoot = spec.renderGraphRoot || this.parent.childRenderRoot;
  if (spec.noBackground) {
    this.childRenderRoot = renderGraphRoot;
  } else {
    this.root = pack.createObject('Transform');
    this.root.parent = g_client.root;
    this.viewInfo = o3djs.rendergraph.createBasicView(
        pack,
        this.root,
        renderGraphRoot);
    this.childRenderRoot = pack.createObject('RenderNode');
    this.childRenderRoot.priority = 100;  // after the normal render graph stuff
    this.childRenderRoot.parent = this.viewInfo.root;

    this.viewInfo.clearBuffer.clearColor = spec.backgroundColor || [0, 0, 1, 1];
    this.viewInfo.root.priority = spec.priority;

    var ui = this.getUI();
    this.updateViewport(ui.getWidth(), ui.getHeight());
  }
};

/**
 * Closes the window.
 */
__Window.prototype.close = function() {
  var windows = [];
  windows.concat(this.subWindows);
  for (var ii = 0; ii < windows.length; ++ii) {
    windows[ii].close();
  }
  this.root.parent = null;
  this.pack.removeObject(this.root);
  this.viewInfo.destroy();
  this.parent.removeWindow(this);
};

/**
 * Removes a child window.
 * @param {Window} child child to remove.
 */
__Window.prototype.removeWindow = function(child) {
  var newWindows = [];
  for (var ii = 0; ii < this.subWindows.length; ++ii) {
    var subWindow = this.subWindows[ii];
    if (subWindow != child) {
      newWindows.push(subWindow);
    }
  }
  this.subWindows = newWindows;
  this.resortWindows();
};

/**
 * Gets the UI that is managing this window.
 * @return {UI} The UI that is managing this window.
 */
__Window.prototype.getUI = function() {
  if (this.parent == null) {
    o3djs.dump.dump('MISSING PARENT for ' + this.title + '\n');
  }
  return this.parent.getUI();
};

/**
 * Adds a child window and initializes it.
 * @param {Object} spec Initalization parameters for the new window.
 * @param {Window} newWindow an uninitialized window.
 */
__Window.prototype.addWindow = function(spec, newWindow) {
  spec.parent = this;
  spec.priority = this.subWindows.length;
  newWindow.init(spec);
  this.subWindows.push(newWindow);
};

/**
 * Creates a new child Window
 * @param {Object} spec Initalization parameters for the new window.
 * @return {Window} the created Window.
 */
__Window.prototype.createWindow = function(spec) {
  var newWindow = new __Window();
  this.addWindow(spec, newWindow);
  return newWindow;
};

/**
 * Gets the number of child windows.
 * @return {number} The number of child Windows.
 */
__Window.prototype.getNumWindows = function() {
  return this.subWindows.length;
};

/**
 * Resorts the windows.
 */
__Window.prototype.resortWindows = function() {
  // This puts us at the end of the array
  this.subWindows.sort(
      function(a, b) { return a.priority - b.priority; });

  // Now go reset all the priorities
  for (var ii = 0; ii < this.subWindows.length; ++ii) {
    this.subWindows[ii].setPriority(ii);
  }
};

/**
 * Sets the priority of a window.
 * @param {number} priority new Priority.
 */
__Window.prototype.setPriority = function(priority) {
  this.priority = priority;
  this.viewInfo.root.priority = priority;
};

/**
 * Gets the absolute position of a window relative to the UI/client area
 * it is on.
 * @return {Point} The absolute position of the window.
 */
__Window.prototype.getAbsolutePosition = function() {
  var position = this.getPosition();
  if (this.parent) {
    this.parent.addPosition(position);
  }
  return position;
};

/**
 * Gets the position of the window. This is a relative to its parent,
 * @return {Point} The relative position of the window.
 */
__Window.prototype.getPosition = function() {
  return createPoint(this.rect.left, this.rect.top);
};

/**
 * Adds the position of this window and all its parents to a point. This
 * effectively returns an absolute position.
 * @param {Point} position Point to add window position to.
 */
__Window.prototype.addPosition = function(position) {
  position.x += this.rect.left;
  position.y += this.rect.top;
  if (this.parent) {
    this.parent.addPosition(position);
  }
};

/**
 * Gets the first parent that is marked as a drag slave.
 * @return {Window} the drag slave or null if non found.
 */
__Window.prototype.getDragSlave = function() {
  if (this.dragSlave) {
    return this;
  }
  if (this.parent) {
    return this.parent.getDragSlave();
  }
  return null;
};

/**
 * Gets the first parent that is marked as a close slave.
 * @return {Window} the close slave or null if non found.
 */
__Window.prototype.getCloseSlave = function() {
  if (this.closeSlave) {
    return this;
  }
  if (this.parent) {
    return this.parent.getCloseSlave();
  }
  return null;
};

/**
 * Moves the window.
 * @param {Point} position position relative to parent to move to.
 */
__Window.prototype.moveWindow = function(position) {
//  o3djs.dump.dump('move: ' + this.title +
//       ' : x = ' + position.x +
//       ' : y = ' + position.y +
//       '\n');
//  o3djs.dump.dump('oldp: ' + this.title +
//       ' : x = ' + this.rect.left +
//       ' : y = ' + this.rect.top +
//       '\n');
  this.rect.left = position.x;
  this.rect.top = position.y;
  var ui = this.getUI();
  this.updateViewport(ui.getWidth(), ui.getHeight());
};

/**
 * Resizes the window
 * @param {Point} size where x = width, y = height.
 */
__Window.prototype.resizeWindow = function(size) {
  var newWidth = size.x < this.minWidth ? this.minWidth : size.x;
  var newHeight = size.y < this.minHeight ? this.minHeight : size.y;
  this.rect.width = newWidth;
  this.rect.height = newHeight;
  var ui = this.getUI();
  this.updateViewport(ui.getWidth(), ui.getHeight());
  for (var ii = 0; ii < this.subWindows.length; ++ii) {
    this.subWindows[ii].onParentResize();
  }
};

/**
 * Responds to the parent being resized.
 */
__Window.prototype.onParentResize = function() {
};


/**
 * Brings a window to the front.
 */
__Window.prototype.toFront = function() {
  // Make us the highest priority.
  this.priority = this.parent.getNumWindows();
  this.parent.resortWindows();
};

/**
 * Brings a window to the back.
 */
__Window.prototype.toBack = function() {
  // Make us the lowest priority.
  this.priority = -1;
  this.parent.resortWindows();
};

/**
 * Handles the mouse getting clicked in our window.
 * Must return true if we are handing the mouse.
 * @param {Point} mousePosition Position of mouse relative to client area.
 * @return {boolean} True if we handled the mouseDown event.
 */
__Window.prototype.onMouseDown = function(mousePosition) {
//  o3djs.dump.dump('title: ' + this.title + '\n');
//  o3djs.dump.dump('checking: x = ' + mousePosition.x +
//                       ' : y = ' + mousePosition.y + '\n');
//  o3djs.dump.dump('children: ' + this.subWindows.length + '\n');
  // Go through all our subwindows.
  for (var ii = this.subWindows.length - 1; ii >= 0; --ii) {
    var subWindow = this.subWindows[ii];
//    o3djs.dump.dump('sw: = ' + subWindow.title + '\n');
//    o3djs.dump.dump('rect: ' +
//         ' l : ' + subWindow.rect.left +
//         ' t : ' + subWindow.rect.top +
//         ' w : ' + subWindow.rect.width +
//         ' h : ' + subWindow.rect.height +
//         '\n');
    if (subWindow.rect.pointInRect(mousePosition)) {
      if (subWindow.onMouseDown(
          subWindow.rect.getRelativePosition(mousePosition))) {
      }
      return true;
    }
  }
  return false;
};

/**
 * Handles the mouse scroll getting scrolled in our window.
 * Must return true if we are handing the mouse.
 * @param {Object} event Information about the mouse.
 * @return {boolean} True if we handled the mouseScroll event.
 */
__Window.prototype.onMouseScroll = function(event) {
//  o3djs.dump.dump('title: ' + this.title + '\n');
//  o3djs.dump.dump('checking: x = ' + mousePosition.x +
//                       ' : y = ' + mousePosition.y + '\n');
//  o3djs.dump.dump('children: ' + this.subWindows.length + '\n');
  // Go through all our subwindows.
  for (var ii = this.subWindows.length - 1; ii >= 0; --ii) {
    var subWindow = this.subWindows[ii];
//    o3djs.dump.dump('sw: = ' + subWindow.title + '\n');
//    o3djs.dump.dump('rect: ' +
//         ' l : ' + subWindow.rect.left +
//         ' t : ' + subWindow.rect.top +
//         ' w : ' + subWindow.rect.width +
//         ' h : ' + subWindow.rect.height +
//         '\n');
    if (subWindow.rect.pointInRect(event.relativeMousePosition)) {
      if (subWindow.onMouseScroll(
          { relativeMousePosition:
                subWindow.rect.getRelativePosition(
                    event.relativeMousePosition),
            scrollAmount: event.scrollAmount})) {
      }
      return true;
    }
  }
  return false;
};

/**
 * Sets the projection matrix of the DrawContext for this window to a
 * perspective matrix taking into account it's viewport might be clipped.
 * @param {number} left The x coordinate of the left plane of the box.
 * @param {number} right The x coordinate of the right plane of the box.
 * @param {number} bottom The y coordinate of the bottom plane of the box.
 * @param {number} top The y coordinate of the right plane of the box.
 * @param {number} near The negative z coordinate of the near plane of the box.
 * @param {number} far The negative z coordinate of the far plane of the box.
 */
__Window.prototype.setOrthographic = function(
    left, right, bottom, top, nearPlane,farPlane) {
  var width = right - left;
  var height = bottom - top;
  var viewRight = this.viewRect_.left + this.viewRect_.width;
  var viewBottom = this.viewRect_.top + this.viewRect_.height;

  this.viewInfo.drawContext.projection = g_math.matrix4.orthographic(
    left + width * this.viewRect_.left,
    right - (1 - viewRight) * width,
    bottom - (1 - viewBottom) * height,
    top + height * this.viewRect_.top,
    nearPlane,
    farPlane);
};

/**
 * Sets the projection matrix of the DrawContext for this window to a
 * perspective matrix taking into account it's viewport might be clipped.
 * @param {number} fovInRad The camera angle from top to bottom (in radians).
 * @param {number} aspectRatio The aspect ratio width / height.
 * @param {number} nearPlane The depth (negative z coordinate)
 *     of the near clipping plane.
 * @param {number} farPlane The depth (negative z coordinate)
 *     of the far clipping plane.
 */
__Window.prototype.setPerspective = function(
    fovInRad, aspectRatio, nearPlane, farPlane) {
  var width = 2 * nearPlane * Math.tan(0.5 * fovInRad) * aspectRatio;
  var height = width / aspectRatio;
  var left = width * -0.5;
  var top = height * -0.5;

  var viewRight = this.viewRect_.left + this.viewRect_.width;
  var viewBottom = this.viewRect_.top + this.viewRect_.height;

  this.viewInfo.drawContext.projection = g_math.matrix4.frustum(
    width * -0.5 + width * this.viewRect_.left,
    width * 0.5 - (1 - viewRight) * width,
    height * -0.5 + (1 - viewBottom) * height,
    height * 0.5 - height * this.viewRect_.top,
    nearPlane,
    farPlane);
};

/**
 * Updates the viewport object to display the correctly for the client areas's
 * resolution.
 * @param {number} o3dClientWidth The width of the client area.
 * @param {number} o3dClientHeight The height of the client area.
 */
__Window.prototype.updateViewport = function(o3dClientWidth,
                                             o3dClientHeight) {
  if (this.viewInfo) {
    var position = this.getAbsolutePosition();

    var left = position.x;
    var top = position.y;
    var width = this.rect.width;
    var height = this.rect.height;

    if (left < 0) {
      width += left;
      left = 0;
    }

    if (top < 0) {
      height += top;
      top = 0;
    }

    var right = left + width;
    var bottom = top + height;

    if (right > o3dClientWidth) {
      width -= right - o3dClientWidth;
      right = o3dClientWidth;
    }

    if (bottom > o3dClientHeight) {
      height -= bottom - o3dClientHeight;
      bottom = o3dClientHeight;
    }

    this.viewRect_ = createRect(
      (left - position.x) / this.rect.width,
      (top - position.y) / this.rect.height,
      width / this.rect.width,
      height / this.rect.height);

    this.clipped_ =
         this.viewRect_.left > 0 ||
         this.viewRect_.top > 0 ||
         this.viewRect_.width < 1 ||
         this.viewRect_.height < 1;

    var vLeft = left / o3dClientWidth;
    var vTop = top / o3dClientHeight;
    var vWidth = width / o3dClientWidth;
    var vHeight = height / o3dClientHeight;

    if (vWidth < 0 || vHeight < 0 || right < 0 || bottom < 0) {
      this.viewInfo.viewport.active = false;
    } else {
      this.viewInfo.viewport.active = true;
      this.viewInfo.viewport.viewport = [vLeft, vTop, vWidth, vHeight];
      if (this.clipped_ || this.clipped_ != this.oldClipped_) {
        this.oldClipped_ = this.clipped_;
        if (this.setProjection) {
          this.setProjection();
        }
      }
    }
  }
  for (var ii = 0; ii < this.subWindows.length; ++ii) {
    this.subWindows[ii].updateViewport(o3dClientWidth, o3dClientHeight);
  }
};

// ************************** AutoSizeWindow **********************************

/***
 * A AutoSizeWindow is a window that keeps its size relative to its parent.
 * and sizes its children.
 * @constructor.
 */
function __AutoSizeWindow() {
  this.wType = "AutoSizeWindow";
}

/**
 * Makes __AutoSizeWindow inherit from Window.
 */
__AutoSizeWindow.prototype = new __Window();

/**
 * Initializes an __AutoSizeWindow window
 * @param {Object} spec Intialization arguments. See __Window.init.
 */
__AutoSizeWindow.prototype.init = function(spec) {
  __Window.prototype.init.call(this, spec);
  this.keepWidth = spec.keepWidth;
  this.keepHeight = spec.keepHeight;
};

/**
 * Responds to the parent being resized.
 */
__AutoSizeWindow.prototype.onParentResize = function() {
  __Window.prototype.onParentResize.call(this);

  var width = this.rect.width;
  var height = this.rect.height;
  if (this.keepWidth) {
    width = this.parent.rect.width;
  }
  if (this.keepHeight) {
    height = this.parent.rect.height;
  }

  var size = createPoint(width, height);

  // only call if the size is different.
  if (width != this.rect.width || height != this.rect.height) {
    this.resizeWindow(size);
    for (var ii = 0; ii < this.subWindows.length; ++ii) {
      this.subWindows[ii].resizeWindow(size);
    }
  }
};

// ************************** DraggableWindow **********************************

/***
 * A DraggableWindow is a window that abstracts Dragging.
 * @constructor.
 */
function __DraggableWindow() {
  this.wType = "DraggableWindow";
}

/**
 * Makes __DraggableWindow inherit from Window.
 */
__DraggableWindow.prototype = new __Window();

/**
 * Initializes an __DraggableWindow.
 * @param {Object} spec Intialization arguments. See __Window.init.
 */
__DraggableWindow.prototype.init = function(spec) {
  __Window.prototype.init.call(this, spec);
};

/**
 * Handles mouse down events.
 * @param {Point} mousePosition position of mouse.
 */
__DraggableWindow.prototype.onMouseDown = function(mousePosition) {
  // TODO(gman): should refactor so mousePosition is relative in mouseMove and
  // mouseUp
//  o3djs.dump.dump('startDragging\n');
  var that = this;
  this.addPosition(mousePosition);
  this.mouseStartPosition = mousePosition;

//  o3djs.dump.dump('mouseStart: ' + this.mouseStartPosition.x +
//       ', ' + this.mouseStartPosition.y + '\n');

  var ui = this.getUI();
  ui.setMouseMoveCallback(function(mousePosition) {
    that.__handleDrag(mousePosition); });
  ui.setMouseUpCallback(function(mousePosition) {
    that.__handleStopDragging(mousePosition); });

  this.onStartDrag(mousePosition);
};

/**
 * Handles mouse move events.
 * @param {Point} mousePosition position of mouse.
 */
__DraggableWindow.prototype.__handleDrag = function(mousePosition) {
//  o3djs.dump.dump('drag ' + this.title +
//       ' : x = ' + mousePosition.x +
//       ' : y = ' + mousePosition.y + '\n');
  var offsetX = mousePosition.x - this.mouseStartPosition.x;
  var offsetY = mousePosition.y - this.mouseStartPosition.y;
//  o3djs.dump.dump('offset ' + this.title +
//       ' : x = ' + offsetX +
//       ' : y = ' + offsetY + '\n');
  this.onDrag(createPoint(offsetX, offsetY));
};

/**
 * Handles on drag events.
 * @param {Point} offset position of mouse.
 */
__DraggableWindow.prototype.onDrag = function(offset) {
  // override this in derived classed.
}

/**
 * Handles mouse up events.
 * @param {Point} mousePosition position of mouse.
 */
__DraggableWindow.prototype.__handleStopDragging = function(mousePosition) {
  var ui = this.getUI();
  ui.setMouseMoveCallback(null);
  ui.setMouseUpCallback(null);
//  o3djs.dump.dump('stop ' + this.title +
//       ' : x = ' + mousePosition.x +
//       ' : y = ' + mousePosition.y + '\n');
  var offsetX = mousePosition.x - this.mouseStartPosition.x;
  var offsetY = mousePosition.y - this.mouseStartPosition.y;
  this.onStopDrag(createPoint(offsetX, offsetY));
};

/**
 * Handles start dragging events.
 */
__DraggableWindow.prototype.onStartDrag = function() {
};

/**
 * Handles stop dragging events.
 */
__DraggableWindow.prototype.onStopDrag = function() {
};

// *************************** DragWindow **********************************

/***
 * A DragWindow is a window that drags its the next highest DragSlaveWindow.
 * @constructor.
 */
function __DragWindow() {
  this.wType = "DragWindow";
}

/**
 * Makes __DragWindow inherit from __DraggableWindow.
 */
__DragWindow.prototype = new __DraggableWindow();

/**
 * Initializes an __DragWindow window
 * @param {Object} spec Intialization arguments. See __DraggableWindow.init.
 */
__DragWindow.prototype.init = function(spec) {
  __DraggableWindow.prototype.init.call(this, spec);
};

/**
 * Handles start drag events.
 * @param {Point} offset position of mouse.
 */
__DragWindow.prototype.onStartDrag = function(offset) {
  this.windowStartPosition = this.getAbsolutePosition();
//  o3djs.dump.dump('windowStart: ' + this.windowStartPosition.x +
//       ', ' + this.windowStartPosition.y + '\n');
};

/**
 * Handles on drag events.
 * @param {Point} offset position of mouse.
 */
__DragWindow.prototype.onDrag = function(offset) {
  var dragSlave = this.getDragSlave();
  if (dragSlave) {
    dragSlave.moveWindow(createPoint(
        this.windowStartPosition.x + offset.x - this.rect.left,
        this.windowStartPosition.y + offset.y - this.rect.top));
  }
};

// *************************** ButtonWindow **********************************

/***
 * A ButtonWindow is a window that implements a button. Specifically if you
 * click on it and THEN release with the pointer still inside its area
 * with will call onClick
 * @constructor.
 */
function __ButtonWindow() {
  this.wType = "ButtonWindow";
}

/**
 * Makes __ButtonWindow inherit from Window.
 */
__ButtonWindow.prototype = new __Window();

/**
 * Initializes an __ButtonWindow window
 * @param {Object} spec Intialization arguments. See __Window.init.
 */
__ButtonWindow.prototype.init = function(spec) {
  __Window.prototype.init.call(this, spec);
};

/**
 * Handles mouse down events.
 * @param {Point} offset position of mouse.
 */
__ButtonWindow.prototype.onMouseDown = function(offset) {
  var that = this;
  var ui = this.getUI();
  ui.setMouseUpCallback(function(mousePosition) {
    that.__handleStopDragging(mousePosition); });
};

/**
 * Handles stop dragging events.
 * @param {Point} mousePosition position of mouse.
 */
__ButtonWindow.prototype.__handleStopDragging = function(mousePosition) {
  var ui = this.getUI();
  ui.setMouseUpCallback(null);
  var position = this.getAbsolutePosition();
  var rect = createRect(position.x,
                        position.y,
                        this.rect.width,
                        this.rect.height);
  if (rect.pointInRect(mousePosition)) {
    this.onClick();
  }
};

// ******************** CloseWindow ***************************************

/***
 * A CloseWindow is a ButtonWindow the closes the closeSlave above it.
 * @constructor.
 */
function __CloseWindow() {
  this.wType = "CloseWindow";
}

/**
 * Makes __CloseWindow inherit from ButtonWindow.
 */
__CloseWindow.prototype = new __ButtonWindow();

/**
 * Initializes an __CloseWindow window
 * @param {Object} spec Intialization arguments. See __Window.init.
 */
__CloseWindow.prototype.init = function(spec) {
  __ButtonWindow.prototype.init.call(this, spec);
};

/**
 * Handles onClick.
 * @param {Point} offset position of mouse.
 */
__CloseWindow.prototype.onClick = function(offset) {
  var closeSlave = this.getCloseSlave();
  if (closeSlave) {
    closeSlave.close();
  }
};

// *************************** ResizeWindow **********************************

/***
 * A ReiszeWindow is a window that resizes its parent from the bottom right
 * corner.
 * * @constructor.
 */
function __ResizeWindow() {
  this.wType = "ResizeWindow";
}

/**
 * Makes __ResizeWindow inherit from DraggableWindow.
 */
__ResizeWindow.prototype = new __DraggableWindow();

/**
 * Initializes an __ResizeWindow window
 * @param {Object} spec Intialization arguments. See __DraggableWindow.init.
 */
__ResizeWindow.prototype.init = function(spec) {
  __DraggableWindow.prototype.init.call(this, spec);
};

/**
 * Handles start drag events.
 * @param {Point} offset position of mouse.
 */
__ResizeWindow.prototype.onStartDrag = function(offset) {
  this.windowStartPosition = this.getPosition();
  this.windowStartRect = this.parent.rect.clone();
//  o3djs.dump.dump('windowStart: ' + this.windowStartPosition.x +
//       ', ' + this.windowStartPosition.y + '\n');
};

/**
 * Handles drag events.
 * @param {Point} offset position of mouse.
 */
__ResizeWindow.prototype.onDrag = function(offset) {
  // Resize our parent.
  this.parent.resizeWindow(createPoint(
      this.windowStartRect.width + offset.x,
      this.windowStartRect.height + offset.y));
  // Move ourselves relative.
  this.moveWindow(createPoint(
      this.parent.rect.width - this.rect.width,
      this.parent.rect.height - this.rect.height))
//  this.moveWindow(createPoint(
//      this.windowStartPosition.x + offset.x,
//      this.windowStartPosition.y + offset.y));
};

// *************************** CanvasWindow **********************************

/***
 * A CanvasWindow is a window that sits on the desktop
 * @constructor.
 */
function __CanvasWindow() {
  this.wType = "CanvasWindow";
}

/**
 * Makes __CanvasWindow inherit from __Window.
 */
__CanvasWindow.prototype = new __Window();

/**
 * Initializes an __CanvasWindow window
 * @param {Object} spec Intialization arguments. See __Window.init.
 */
__CanvasWindow.prototype.init = function(spec) {
  spec.dragSlave = true;
  spec.closeSlave = true;
  __Window.prototype.init.call(this, spec);
  var bgColor = [
    Math.random() * 0.3 + 0.1,
    Math.random() * 0.3 + 0.1,
    Math.random() * 0.3 + 0.1,
    1];
  this.autoSizeModel = new __AutoSizeWindow();
  this.addWindow({ title: 'autoSizeModel',
                   rect: createRect(0,
                                    0,
                                    spec.rect.width,
                                    spec.rect.height),
                   noBackground: true,
                   keepWidth: true,
                   keepHeight: true },
                   this.autoSizeModel);
  this.appWindow = spec.appWindow;
  this.autoSizeModel.addWindow({ title: 'app',
                                 rect: createRect(0,
                                                  0,
                                                  spec.rect.width,
                                                  spec.rect.height),
                                 backgroundColor: bgColor },
                               this.appWindow);


  this.autoSizeTitle = new __AutoSizeWindow();
  this.addWindow({ title: 'autoSizeTitle',
                   rect: createRect(0,
                                    0,
                                    spec.rect.width,
                                    20),
                   noBackground: true,
                   keepWidth: true },
                 this.autoSizeTitle);

  this.titleBar = new __DragWindow();
  this.autoSizeTitle.addWindow({ title: 'title',
                                 rect: createRect(0,
                                                  0,
                                                  spec.rect.width,
                                                  20),
                                backgroundColor: [0.8, 0.8, 0.8, 1.0] },
                                this.titleBar);
  this.resizeTab = new __ResizeWindow();
  this.addWindow({ title: 'resize',
                   rect: createRect(spec.rect.width - 15,
                                    spec.rect.height - 15,
                                    15,
                                    15),
                 backgroundColor: [1, 1, 1, 1] },
                 this.resizeTab);

  this.closeButton = new __CloseWindow();
  this.addWindow({ title: 'close',
                   rect: createRect(2, 2, 12, 12),
                   backgroundColor: [0, 0, 0, 1] },
                 this.closeButton);

  this.shadow = WindowShadow(this.pack);
    // Create a material.
  var myMaterial = this.pack.createObject('Material');

  // Load the effect for our sphere from our textarea.
  var defaultEffect = this.pack.createObject('Effect');
  var fxString = document.getElementById('shadow').value;
  defaultEffect.loadFromFXString(fxString);

  // Apply our effect to this material. The effect tells the 3D hardware
  // which shader to use.
  myMaterial.effect = defaultEffect;
  defaultEffect.createUniformParameters(myMaterial);

  // Attach the cube to the root of this window's scene graph.
  this.shadowTransform = this.pack.createObject('Transform');
  this.shadowTransform.parent = this.parent.root;
  this.shadowTransform.addShape(this.shadow.shape);

  this.shadow.shape.elements[0].material = myMaterial;

  // Generate the draw elements.
  this.shadow.shape.createDrawElements(this.pack, null);
  this.shadow.setSize(this.rect.width, this.rect.height, 5);
  this.shadowTransform.translate(this.rect.left + 2, 0, -(this.rect.top + 2));

  this.shadowViewInfo = o3djs.rendergraph.createBasicView(
        this.pack,
        this.shadowTransform,
        this.parent.childRenderRoot);
  this.shadowViewInfo.root.priority = this.priority - 0.5;
  this.shadowViewInfo.clearBuffer.clearColorFlag = false;

  // Set the material's drawList
  myMaterial.drawList = this.shadowViewInfo.zOrderedDrawList;

  // Eye-position, this is where our camera is at.
  var eye = [0, -10, 0];
  this.shadowViewInfo.zOrderedState.getStateParam('CullMode').value =
      g_o3d.State.CULL_NONE;

  // Target, this is where our camera is pointed at.
  var target = [0, 0, 0];

  // Up-vector, this tells the camera which direction is 'up'.
  // We define the positive y-direction to be up in this example.
  var up = [0, 0, 1];

  this.shadowViewInfo.drawContext.view = g_math.matrix4.lookAt(eye, target, up);
  var ui = this.getUI();
  this.shadowViewInfo.drawContext.projection = g_math.matrix4.orthographic(
      0,
      ui.getWidth(),
      -ui.getHeight(),
      0,
      0.1,
      1000);
};

/**
 * Closes the window.
 */
__CanvasWindow.prototype.close = function() {
  this.shadowTransform.parent = null;
  this.shadowViewInfo.destroy();
  __Window.prototype.close.call(this);
};

/**
 * Sets the priority of the window
 * @param {number} priority new Priority of window.
 */
__CanvasWindow.prototype.setPriority = function(priority) {
  __Window.prototype.setPriority.call(this, priority);
  this.shadowViewInfo.root.priority = this.priority - 0.5;
};

/**
 * Updates the viewport.
 * @param {number} width Width of client area.
 * @param {number} height Height of client area.
 */
__CanvasWindow.prototype.updateViewport = function(width, height) {
  __Window.prototype.updateViewport.call(this, width, height);
  if (this.shadowViewInfo) {
    this.shadowViewInfo.drawContext.projection =
        g_math.matrix4.orthographic(0, width, -height, 0, 0.1, 1000);
  }
};

/**
 * Resizes this window.
 * @param {Point} size where x = width, y = height.
 */
__CanvasWindow.prototype.resizeWindow = function(size) {
  __Window.prototype.resizeWindow.call(this, size);
  this.shadow.setSize(this.rect.width, this.rect.height, 5);
};

/**
 * Moves this window.
 * @param {Point} position New Position of window.
 */
__CanvasWindow.prototype.moveWindow = function(position) {
  __Window.prototype.moveWindow.call(this, position);
  this.shadowTransform.identity();
  this.shadowTransform.translate(this.rect.left + 2, 0, -(this.rect.top + 2));
};

/**
 * Handles the mouse getting clicked in our window.
 * Must return true if we are handing the mouse.
 * @param {Point} mousePosition Position of mouse relative to client area.
 * @return {boolean} True if handled.
 */
__CanvasWindow.prototype.onMouseDown = function(mousePosition) {
  if (this.priority != this.parent.getNumWindows() - 1) {
    this.toFront();
  }
  return __Window.prototype.onMouseDown.call(this, mousePosition);
};

/**
 * Brings a window to the front.
 */
__CanvasWindow.prototype.toFront = function() {
  // Make us the highest priority.
  this.priority = this.parent.getNumWindows();
  this.parent.resortWindows();
  this.shadow.setSize(this.rect.width, this.rect.height, 5);
};

/**
 * Brings a window to the front.
 */
__CanvasWindow.prototype.toBack = function() {
  // Make us the lowest priority.
  this.priority = -1;
  this.parent.resortWindows();
};

// *************************** BackWindow **********************************

/***
 * A Backindow is a window that IS the desktop.
 * @constructor.
 */
function __BackWindow() {
  this.wType = "BackWindow";
}

/**
 * Makes __BackgWindow inherit from __Window.
 */
__BackWindow.prototype = new __Window();

/**
 * Initializes an __BackWindow window
 * @param {Object} spec Intialization arguments. See __Window.init.
 */
__BackWindow.prototype.init = function(spec) {
  this.ui = spec.ui;
  __Window.prototype.init.call(this, spec);
};

/**
 * Gets the UI of the window.
 * @return {UI} The UI of the window.
 */
__BackWindow.prototype.getUI = function() {
  return this.ui;
};


